/*
 * MRTDFunctions.java
 *
 * Created on 25. September 2007
 *
 *  This file is part of JSmex.
 *
 *  JSmex is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  JSmex is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Foobar; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

package jsmex.function;

import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Vector;
import javax.swing.JOptionPane;
import jsmex.JSmexTools;
import jsmex.LogTracer;
import jsmex.cardservice.MRTDCardService;
import jsmex.gui.MRTDGUI;
import opencard.core.terminal.ResponseAPDU;
import opencard.core.util.HexString;

/**
 *
 * @author Tobias Senger  (jsmex@t-senger.de)
 */
public class MRTDFunctions {
    
    private MRTDGUI gui = null;
    private MRTDCardService cs = null;
    private LogTracer lt = null;
    
    private boolean accessConditionsSatisfied = false; 
    
    private EFCOMDataContainer efcom = null;
    private DG1DataContainer dg1 = null;
    private DG2DataContainer dg2 = null;
    private DG14DataContainer dg14 = null;
    private EFSODDataContainer efsod = null;
    
    
    
    /** Creates a new instance of MRTDFunctions */
    public MRTDFunctions(MRTDCardService cs, LogTracer ltracer) {
        this.cs = cs;
        this.lt = ltracer;
        cs.selectMrtdApp();
        showGUI();
    }
    
    /**Show MRTD-GUI
     *
     */
    public void showGUI() {
        lt.info("called method showGUI().", this);
        if (gui == null) gui = new MRTDGUI(this);
        gui.setVisible(true);
    }
    
    /**Close MRTD-GUI
     *
     */
    public void closeGUI() {
        lt.info("called method closeGUI().", this);
        gui.setVisible(false);
        gui.dispose();
        gui = null;
    }
    
    private void doBAC(String mrz) throws Exception{
         
        lt.info("doBAC("+mrz+")",this);
        String mrzInfo = calculateMrzInfo(mrz);
        lt.info("MRZINFO : "+mrzInfo,this);
            
        cs.doMutualAuthentication(mrzInfo);
     
        accessConditionsSatisfied = true;
    }

    private String calculateMrzInfo(String mrz) {
        String documentNr = mrz.substring(0,10); // +1 checkdiget
        String dateOfBirth = mrz.substring(13,20); // +1 checkdiget
        String dateOfExpiry = mrz.substring(21,28); // +1 checkdiget
        String mrzInfo = documentNr + dateOfBirth + dateOfExpiry;
        return mrzInfo;
    }
    
    public EFCOMDataContainer getEFCOM(String mrz2) throws Exception{
        if (efcom!=null) return efcom;
        
        if (cs.isBacProtected()) {
            while (!accessConditionsSatisfied) doBAC(mrz2);
        }
        
        ResponseAPDU resp = null;
       
        resp = cs.selectEF(new byte[] {(byte) 0x01, (byte) 0x1E}); //FID EF.COM is 0x011E
        if (resp.sw()==0x9000) {
            resp = cs.readBinary((byte)0,(byte)0,(byte)0x00); //read all
        }
     
        
        efcom = new EFCOMDataContainer(resp.data());
        
        return efcom;
        
    }
    
    public DG1DataContainer getDG1(String mrz2) throws Exception{
        
        if (dg1!=null) return dg1;
        
        lt.info("MRZ2: "+mrz2,this);
        
        
        
        if (cs.isBacProtected()) {
            while (!accessConditionsSatisfied) doBAC(mrz2);
        }
        
        ResponseAPDU resp = null;
       
        resp = cs.selectEF(new byte[] {(byte) 0x01, (byte) 0x01}); //FID EF.DG1 is 0x0101
        if (resp.sw()==0x9000) {
                resp = cs.readBinary((byte)0,(byte)0,(byte)0x5D); //Le: 88 Bytes MRZ + 5 Bytes Tags = 92 Bytes (= 0x5D)
        }        
        dg1 = new DG1DataContainer(resp.data());
        
        return dg1;
     
    }

    public DG2DataContainer getDG2(String mrz2) throws Exception {
        
        if (dg2!=null) return dg2;
        
        while (!accessConditionsSatisfied) doBAC(mrz2);
        
        ResponseAPDU resp = null;
        
        try {
            resp = cs.selectEF(new byte[] {(byte) 0x01, (byte) 0x02}); //FID EF.DG2 is 0x0102
            if (resp.sw()==0x9000) {
                resp = cs.readBinary((byte)0,(byte)0,(byte)0x08);
            }
            else System.out.println("getDG2Data: SW was not 0x9000");
        } catch (Exception ex) {
            //TODO Irgendeine Meldung an das GUI senden
            lt.warning(ex.getMessage(),this);
        }
        
        int dg2Length = (int) (JSmexTools.toUnsignedInt(resp.data()[2])*256+JSmexTools.toUnsignedInt(resp.data()[3])+4);
        
        int i = 0;
        byte[] dg2Data = new byte[dg2Length];
     
        do {
            try {
                resp = cs.readBinary((byte) i, (byte) 0, (byte) 0x100);
                if (resp.data()==null) break;
                System.arraycopy(resp.data(),0,dg2Data,i*0x100,resp.data().length);
                i++;
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        } while (resp.sw1()==(byte)0x90);
        
        dg2 = new DG2DataContainer(dg2Data);
        
        return dg2;
    }
    
    public DG14DataContainer getDG14(String mrz2) throws Exception {
        
        if (dg14!=null) return dg14;
        
        if (cs.isBacProtected()) {
            while (!accessConditionsSatisfied) doBAC(mrz2);
        }
        
        ResponseAPDU resp = null;
        
        resp = cs.selectEF(new byte[] {(byte) 0x01, (byte) 0x0E}); //FID EF.DG14 is 0x010E
        if (resp.sw()==0x9000) {
            resp = cs.readBinary((byte)0,(byte)0,(byte)0x08);
        }
        else System.out.println("getDG14Data: SW was not 0x9000");
       
        int dg14Length = (int) (JSmexTools.toUnsignedInt(resp.data()[2])*256+JSmexTools.toUnsignedInt(resp.data()[3])+4);
        
        
        int i = 0;
        byte[] dg14Data = new byte[dg14Length];
     
        do {
            try {
                resp = cs.readBinary((byte) i, (byte) 0, (byte) 0x100);
                if (resp.data()==null) break;
                System.arraycopy(resp.data(),0,dg14Data,i*0x100,resp.data().length);
                i++;
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        } while (resp.sw1()==(byte)0x90);
        
        dg14 = new DG14DataContainer(dg14Data);
        
        return dg14;
    }
    
    public EFSODDataContainer getEFSOD(String mrz2) throws Exception {
        
        if (efsod!=null) return efsod;
        
        if (cs.isBacProtected()) {
            while (!accessConditionsSatisfied) doBAC(mrz2);
        }
        
        ResponseAPDU resp = null;
        
        resp = cs.selectEF(new byte[] {(byte) 0x01, (byte) 0x1D}); //FID EF.DG14 is 0x011D
        if (resp.sw()==0x9000) {
            resp = cs.readBinary((byte)0,(byte)0,(byte)0x08);
        }
        else System.out.println("getEFSODData: SW was not 0x9000");
       
        int efSodLength = (int) (JSmexTools.toUnsignedInt(resp.data()[2])*256+JSmexTools.toUnsignedInt(resp.data()[3])+4);
        
        
        int i = 0;
        byte[] efSodData = new byte[efSodLength];
     
        do {
            try {
                resp = cs.readBinary((byte) i, (byte) 0, (byte) 0x100);
                if (resp.data()==null) break;
                System.arraycopy(resp.data(),0,efSodData,i*0x100,resp.data().length);
                i++;
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        } while (resp.sw1()==(byte)0x90);
        
        efsod = new EFSODDataContainer(efSodData);
        
        return efsod;
    }
    
    public boolean dataToFile(String prefix) {
        
        if (efcom!=null) saveToFile(prefix+"_EF_COM.bin",efcom.getBytes());
        if (dg1!=null) saveToFile(prefix+"_DG1_DATA.bin",dg1.getBytes());
        if (dg2!=null) {
            saveToFile(prefix+"_DG2_DATA.bin",dg2.getBytes());
            saveToFile(prefix+"_image.jp2",dg2.getImageBytes());
        }
        if (dg14!=null) saveToFile(prefix+"_DG14_DATA.bin",dg14.getBytes()); 
        if (efsod!=null) saveToFile(prefix+"_EF_SOD.bin",efsod.getBytes()); 
        
        return true;
    }
    
    public Image getFacialImage(String mrz2) throws Exception {
        return getDG2(mrz2).getImage().getScaledInstance(188,250,Image.SCALE_SMOOTH);
    }
   
     /**Saves data with the name given in parameter efName into a local file.
     *
     * @param efName The Name of the EF.
     * @param data
     * @return Returns 'true' if the record were saved to a local file on hd.
     */
    private boolean saveToFile(String efName, byte[] data) {
        
        boolean success = false;
        lt.info("called method saveToFile(efName: "+efName+", byte[] data).", this);
        
        try {
            File file = new File(efName);
            
            if (file != null) {
                FileOutputStream fos = new FileOutputStream( file );
                fos.write(data);
                fos.close();
                success = true;
            } else success = false;
        } catch ( Exception e ) { e.printStackTrace(); }
        return success;
    }       

    public void saveMRZList(String mrz1, String mrz2) {
        try {
            BufferedWriter outputStream = new BufferedWriter(new FileWriter(jsmex.JSmexStatics.mrzListFileName,true));
            outputStream.write(mrz1+";"+mrz2+"\n");
            outputStream.close();
        } catch (IOException ex) {
            System.out.println("Error while saving MrzList");
        }
    }
    
    public Vector getMRZList() {
        
        Vector vec = null;
        String tempString = null;
        String[] stringArray = new String[2];
        try {
            long fileSize = new File(jsmex.JSmexStatics.mrzListFileName).length();
            vec = new Vector((int)(fileSize/90));
            BufferedReader inputStream = new BufferedReader(new FileReader(jsmex.JSmexStatics.mrzListFileName));
            do {
                tempString = inputStream.readLine();
                stringArray = tempString.split(";",2);
                vec.add(stringArray);
            } while (tempString!=null); 
            inputStream.close();
        } catch (IOException ex) {
            System.out.println("Error while reading/parsing MrzList");
        } finally {
            return vec;
        }
    }
}
